﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ShadowCommando.Game;
using System.Drawing;
using ShadowCommando.Game.Elements;
namespace ShadowCommando.Game.GameBoard
{
    class Board
    {
        public Board(int _width, int _height, int _size)
        {
            width = _width;
            height = _height;
            size = _size;
            skew = (int)Math.Sqrt(2 * (size * size));
            boardWidth = width / size;
            boardHeight = height / size;
            fields = new Field[boardHeight, boardWidth];
            for (int i = 0; i < boardHeight; i++)
            {
                for (int j = 0; j < boardWidth; j++)
                {
                    fields[i, j] = new Field(size * j, size * i, j, i, true);
                }
            }
        }
        #region Elements

        Field[,] fields;

        int size;
        int skew;

        int boardWidth;
        int boardHeight;
        int width;
        int height;
        #endregion
        #region Properties
        public int Width
        {
            get
            {
                return width;
            }
            set
            {
                width = value;
            }
        }
        public int Height
        {
            get
            {
                return height;
            }
            set
            {
                height = value;
            }
        }
        #endregion

        #region Functions
        public void SetField(Element element, bool _movable)
        {
            ElementId id = element.ElementId;
            for (int i = 0; i < element.BoardHeight; i++)
            {
                for (int j = 0; j < element.BoardWidth; j++)
                {
                    fields[element.BoardY + i, element.BoardX + j].ElementId = id;
                    fields[element.BoardY + i, element.BoardX + j].Obstacle = _movable;
                }
            }
        }
        public void SetField<T>(List<T> elements) where T : Element
        {
            foreach (Element e in elements)
            {
                for (int i = 0; i < e.BoardHeight; i++)
                {
                    for (int j = 0; j < e.BoardWidth; j++)
                    {
                        fields[e.BoardY + i, e.BoardX + j].ElementId = e.ElementId;
                        fields[e.BoardY + i, e.BoardX + j].Obstacle = e.Movable;
                    }
                }
            }
        }
        public void SetElement(Element elemet)
        {
            elemet.BoardLocation = new Point(elemet.FakeX / size, elemet.FakeY / size);
            elemet.BoardWidth = elemet.Width / size;
            elemet.BoardHeight = elemet.Height / size;
        }
        private int H(Point p1, Point p2)
        {
            return (Math.Abs(p1.X - p2.X) + Math.Abs(p1.Y - p2.Y)) * size;
        }
        private int G(Point p1, Point p2)
        {
            if (p1 == p2)
                return 0;
            return p1.X == p2.X || p1.Y == p2.Y ? size : skew;
        }
        public List<Point> Astar(Point s, Point t, Element element)
        {
            PriorityQueue<Field> open = new PriorityQueue<Field>();
            List<Field> close = new List<Field>();
            Field actual = fields[s.Y, s.X];
            actual.H = H(s, t);
            actual.G = int.MaxValue / 2;
            actual.Parent = actual;
            open.Enqueue(actual);
            if (HasElement(element))
                return null;
                while (open.Count != 0)
                {
                    if (close == null)
                        return null;
                    if (actual.BoardPoint == t)
                        break;
                    actual = open.Dequeue();
                    close.Add(actual);
                    Actualize(actual, fields[t.Y, t.X], ref open, ref close, element);
                }
            if (actual.BoardPoint != t || close.Count == 0)
                return null;

            List<Point> path = new List<Point>();
            actual = close[close.Count - 1];
            while (true)
            {
                if (actual.BoardPoint == s)
                    break;

                path.Add(actual.FakePoint);
                actual = actual.Parent;
            }

            return path;
        }
        private void Actualize(Field parent, Field t, ref PriorityQueue<Field> open, ref List<Field> close, Element element)
        {
            Field actual;
            int x = parent.BoardX - 1;
            int y = parent.BoardY - 1;

            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    if (y < 0 || y > boardHeight - 1 || x < 0 || x > boardWidth - 1)
                    {
                        close = null;
                        return;
                    }
                    actual = fields[y + i, x + j];


                    if (close.Count > 2000)
                    {
                        close = null;
                        return;
                    }

                    if (close.Contains(actual) || actual.Obstacle||fields[actual.BoardY, actual.BoardX + element.BoardWidth-1].Obstacle
                        || fields[actual.BoardY + element.BoardHeight-1, actual.BoardX].Obstacle || fields[actual.BoardY + element.BoardHeight-1, actual.BoardX + element.BoardWidth-1].Obstacle)
                    {
                        continue;
                    }
                    else if (!open.Contains(actual))
                    {
                        actual.H = H(actual.BoardPoint, t.BoardPoint);
                        actual.G = G(actual.BoardPoint, parent.BoardPoint) + parent.G;
                        actual.Parent = parent;
                        open.Enqueue(actual);
                    }
                    else
                    {
                        if (actual.G < G(actual.BoardPoint, parent.BoardPoint))
                        {
                            actual.G = G(actual.BoardPoint, parent.BoardPoint) + parent.G;
                            actual.Parent = parent;

                        }
                    }


                }

            }
        }
        public bool Movable(Point point)
        {
            return fields[point.Y, point.X].Obstacle;
        }
        public bool HasElement(Element element)
        {
            for (int i = 0; i < element.BoardHeight; i++)
            {
                for (int j = 0; j < element.BoardWidth ; j++)
                {
                    try
                    {

                    
                    if (fields[element.BoardY + i , element.BoardX + j ].Obstacle)
                    {
                        return true;
                    }
                    }
                    catch (IndexOutOfRangeException)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        #endregion

    }
}
